package cz.proks.schoolpupilbook.gwt.widget.common;

import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;

import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.cell.client.Cell;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DecoratedPopupPanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.AsyncDataProvider;
import com.google.gwt.view.client.HasData;
import com.google.gwt.view.client.SingleSelectionModel;
import com.google.gwt.view.client.TreeViewModel;

import cz.proks.schoolpupilbook.gwt.core.CookieHelper;
import cz.proks.schoolpupilbook.gwt.core.ResultNotifiable;
import cz.proks.schoolpupilbook.gwt.core.SchoolPupilBookFactory;
import cz.proks.schoolpupilbook.gwt.core.Test;
import cz.proks.schoolpupilbook.gwt.service.ListService;
import cz.proks.schoolpupilbook.gwt.service.MarkService;
import cz.proks.schoolpupilbook.gwt.service.ServiceFactory;
import cz.proks.schoolpupilbook.shared.domain.Course;
import cz.proks.schoolpupilbook.shared.domain.Mark;
import cz.proks.schoolpupilbook.shared.domain.MarkComment;
import cz.proks.schoolpupilbook.shared.domain.User;

public class ParentView extends Composite {

	interface ParentViewUiBinder extends UiBinder<Widget, ParentView> {
	}
	
	private static ParentViewUiBinder uiBinder = GWT.create(ParentViewUiBinder.class);

	@UiField(provided = true) PupilCellBrowser browser;
	@UiField Button hiddenNewCommentButton;
	
	private ParentTreeModel model;
	
	public ParentView(User user) {
		PupilCellBrowser.Resources resources = GWT.create(PupilCellBrowser.Resources.class);
		
		this.model = new ParentTreeModel(user);
		browser = new PupilCellBrowser(this.model, null, resources);
		browser.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.ENABLED);
		browser.setAnimationEnabled(true);
		browser.setWidth("100%");
		browser.setDefaultColumnWidth(260);
		browser.setMinimumColumnWidth(120);
		
		initWidget(uiBinder.createAndBindUi(this));
		
		initButton();		
		consumeEvents();
	}
	
	private void initButton() {
		hiddenNewCommentButton.getElement().setId("hiddenNewCommentButton");
		hiddenNewCommentButton.addClickHandler(new ClickHandler() {			
			@Override
			public void onClick(ClickEvent event) {
				new CommentPopupPanel(model.getSelectedMark()).center();				
			}
		});
	}
	
	private void consumeEvents() {
		SchoolPupilBookFactory.getEventBus().addHandler(CommentSubmitEvent.TYPE, new CommentSubmitEvent.CommentSubmitHandler() {			
			@Override
			public void onNewComment(CommentSubmitEvent event) {
				handleAddedCommentEvent(event);				
			}
		});
	}
	
	private void handleAddedCommentEvent(CommentSubmitEvent event) {
		model.refresh();
	}
	
	public static class ParentTreeModel implements TreeViewModel {
		private final SingleSelectionModel<User> childSelection;
		private final SingleSelectionModel<Course> courseSelection;
		private final SingleSelectionModel<SimpleMarkUI> markSelection;
		private final User user;
		private RefreshableAsyncDataProvider<?> currentDataProvider;		
		
		public ParentTreeModel(User user) {
			childSelection = new SingleSelectionModel<User>();
			courseSelection = new SingleSelectionModel<Course>();
			markSelection = new SingleSelectionModel<SimpleMarkUI>();
			this.user = user;
		}
						
		@Override
		public <T> NodeInfo<?> getNodeInfo(T value) {
			// Start point - list childs
			if (value == null) {
				RefreshableAsyncDataProvider<User> dataProvider = new StudentDataProvider(user);
				currentDataProvider = dataProvider;
				
				Cell<User> cell = new ChildCell();
				
				return new DefaultNodeInfo<User>(dataProvider, cell, childSelection, null);
			}
			
			else if (value instanceof User) {
				RefreshableAsyncDataProvider<Course> dataProvider = new CourseDataProvider(getSelectedUser());
				currentDataProvider = dataProvider;
				
				Cell<Course> cell = new CourseCell();
				return new DefaultNodeInfo<Course>(dataProvider, cell, courseSelection, null);
			}
			
			else if (value instanceof Course) {
				RefreshableAsyncDataProvider<SimpleMarkUI> dataProvider = new SimpleMarkDataProvider(getSelectedUser(), getSelectedCourse());
				currentDataProvider = dataProvider;
				
				Cell<SimpleMarkUI> cell = new SimpleMarkCell();
				return new DefaultNodeInfo<SimpleMarkUI>(dataProvider, cell, markSelection, null);	
			}
			
			else if (value instanceof SimpleMarkUI) {
				RefreshableAsyncDataProvider<Mark> dataProvider = new MarkDataProvider(((SimpleMarkUI) value).getMark().getId());
				currentDataProvider = dataProvider;
				
				Cell<Mark> cell = new MarkCell();						
				return new DefaultNodeInfo<Mark>(dataProvider, cell);
			}

			return null;
		}		

		@Override
		public boolean isLeaf(Object value) {
			return value instanceof Mark;
		}
		
		public void refresh() {
			if (currentDataProvider != null)
				currentDataProvider.refresh();
		}
		
		public User getSelectedUser() {
			return childSelection.getSelectedObject();
		}
		
		public Course getSelectedCourse() {
			return courseSelection.getSelectedObject();
		}
		
		public Mark getSelectedMark() {
			return markSelection.getSelectedObject().getMark();
		}
		
		private LinkedList<SimpleMarkUI> wrapMarkList(LinkedList<Mark> markList){
			LinkedList<SimpleMarkUI> simpleMarkList = new LinkedList<ParentView.SimpleMarkUI>();
			
			if (markList == null)
				return simpleMarkList;
			
			for (Mark mark : markList) {
				simpleMarkList.add(new SimpleMarkUI(mark));
			}
			
			return simpleMarkList;
		}
	}
	
	private static class SimpleMarkUI implements Comparable<SimpleMarkUI>{
		private final DateTimeFormat timeFormater = DateTimeFormat.getFormat("dd.MM.yyyy HH:mm");
		private Mark mark;

		public SimpleMarkUI(Mark mark) {
			super();
			this.mark = mark;
		}
		
		public Mark getMark() {
			return mark;
		}
		
		public Date getTime() {
			return mark.getTime();
		}
		
		public String getFormatedTime() {
			return timeFormater.format(getTime());
		}
		
		@Override
		public int compareTo(SimpleMarkUI o) {
			return o == null ? 1 : getMark().compareTo(o.getMark());
		}
	}
	
	private static class ChildCell extends AbstractCell<User> {
		@Override
		public void render(Context context, User value, SafeHtmlBuilder sb) {
			if (value != null)
				sb.appendEscaped(value.getFullName());			
		}		
	}
	
	private static class CourseCell extends AbstractCell<Course> {
		@Override
		public void render(Context context, Course value, SafeHtmlBuilder sb) {
			if (value != null)
				sb.appendEscaped(value.getName());			
		}		
	}	
	
	private static class SimpleMarkCell extends AbstractCell<SimpleMarkUI> {
		@Override
		public void render(Context context, SimpleMarkUI value, SafeHtmlBuilder sb) {
			if (value != null)
				sb.appendEscaped(value.getFormatedTime());			
		}		
	}
	
	private static class MarkCell extends AbstractCell<Mark> {
		private final DateTimeFormat timeFormater = DateTimeFormat.getFormat("dd.MM.yyyy HH:mm");
							
		@Override
		public void render(Context context, Mark value, SafeHtmlBuilder sb) {
			if (value != null) {				 
				sb.appendHtmlConstant("<table><tr><td>Lector: <b>");
				sb.appendEscaped(value.getLector().getFullName());
				sb.appendHtmlConstant("</b></td></tr><tr><td>Mark: <b>");
				sb.appendEscaped(value.getMark());
				sb.appendHtmlConstant("</b></td></tr><tr><td>");
				appendComments(value, sb);
				sb.appendHtmlConstant("</td></tr><tr><td>");
				appendNewCommentButton(value, sb);
				sb.appendHtmlConstant("</td></tr></table>");
			}									
		}
		
		private void appendComments(Mark mark, SafeHtmlBuilder sb) {
			sb.appendEscaped("Comments: ");
			
			if (mark.getComments() == null || mark.getComments().isEmpty())
				sb.appendHtmlConstant("<b>").appendEscaped("None!").appendEscaped("</b>");
			
			else {
				Collections.sort(mark.getComments());
				sb.appendHtmlConstant("<ul>");
				
				for (MarkComment comment : mark.getComments()) {
					sb.appendHtmlConstant("<li>")
						.appendHtmlConstant("<b>").appendEscaped(timeFormater.format(comment.getTime())).appendHtmlConstant("</b><br>")						
						.appendHtmlConstant("<b>").appendEscaped(comment.getContributor().getFullName()).appendHtmlConstant("</b>")
						.appendEscaped(": ")
						.appendEscaped(comment.getComment())
					  .appendHtmlConstant("</li>");
				}
				
				sb.appendHtmlConstant("</ul>");
			}
		}
		
		private void appendNewCommentButton(final Mark mark, SafeHtmlBuilder sb) {			
						
			Button newCommentButton = new Button("New Comment");
			newCommentButton.getElement().setAttribute("onClick", "document.getElementById('hiddenNewCommentButton').click();");			
			newCommentButton.addStyleName("button");
			newCommentButton.addStyleName("green");
			
			sb.appendHtmlConstant(newCommentButton.toString());
		}
	}		
	
	public static class CommentPopupPanel extends DecoratedPopupPanel {
		private final Mark mark;		
		
		public CommentPopupPanel(Mark mark) {
			super(true);
			this.mark = mark;			
						
			init();
		}
		
		private void init() {			
			addStyleName("rounded-corners");
			
			VerticalPanel panel = new VerticalPanel();
			HorizontalPanel hPanel = new HorizontalPanel();
			
			final Label comment = new Label("Add your comment here:");
			final TextArea textArea = new TextArea();
			final Button backButton = new Button("Back");
			final Button submitButton = new Button("Submit");
			
			backButton.addStyleName("button");
			backButton.addStyleName("green");
			
			submitButton.addStyleName("button");
			submitButton.addStyleName("green");
			
			
			hPanel.add(backButton);
			hPanel.add(submitButton);
			hPanel.setSpacing(5);
			
			panel.add(comment);
			panel.add(textArea);
			panel.add(hPanel);			
			
			backButton.addClickHandler(new ClickHandler() {				
				@Override
				public void onClick(ClickEvent event) {					
					closePopup();
				}
			});
			
			submitButton.addClickHandler(new ClickHandler() {				
				@Override
				public void onClick(ClickEvent event) {
					 User user = CookieHelper.getLoggedUser();
					 
					 CommentSubmitEvent submitEvent = new CommentSubmitEvent(mark, user, textArea.getText());
					 SchoolPupilBookFactory.getEventBus().fireEvent(submitEvent);
					 
					 mark.addComment(user, new Date(), textArea.getText());					 
					 closePopup();
				}
			});
			
			setWidget(panel);
		}
		
		private void closePopup() {
			this.hide(true);
		}
	}
	
	public static class CommentSubmitEvent extends GwtEvent<CommentSubmitEvent.CommentSubmitHandler> {
		public interface CommentSubmitHandler extends EventHandler {
			void onNewComment(CommentSubmitEvent event);
		}
		
		public static Type<CommentSubmitHandler> TYPE = new Type<ParentView.CommentSubmitEvent.CommentSubmitHandler>();
		
		private Mark mark;
		private User user;
		private String comment;
		
		public CommentSubmitEvent(Mark mark, User user, String comment) {
			super();
			this.mark = mark;
			this.user = user;
			this.comment = comment;
		}

		@Override
		public Type<CommentSubmitHandler> getAssociatedType() {
			return TYPE;
		}

		@Override
		protected void dispatch(CommentSubmitHandler handler) {
			handler.onNewComment(this);
		}

		public Mark getMark() {
			return mark;
		}				
		
		public User getUser() {
			return user;
		}

		public String getComment() {
			return comment;
		}
	}
	
	private static abstract class RefreshableAsyncDataProvider<T> extends AsyncDataProvider<T> {
		public void refresh() {			
			onRangeChanged(null);
		}
	}
	
	private static class StudentDataProvider extends RefreshableAsyncDataProvider<User> {
		private User parent;

		public StudentDataProvider(User parent) {
			super();
			this.parent = parent;
		}
		
		@Override
		protected void onRangeChanged(HasData<User> display) {
			getData();			
		}
		
		private void getData() {
			ListService listService = ServiceFactory.getListService();
			listService.listChilds(parent, new ResultNotifiable<LinkedList<User>>() {				
				@Override
				public void onSucces(LinkedList<User> object) {
					updateRowData(0, object);
					updateRowCount(object.size(), true);
				}
				
				@Override
				public void onError(Throwable t) {
					Window.alert("Communication failed: " + t.getLocalizedMessage());
				}
			});			
		}
	}
	
	private static class CourseDataProvider extends RefreshableAsyncDataProvider<Course> {
		private User user;
				
		public CourseDataProvider(User user) {
			super();
			this.user = user;
		}

		@Override
		protected void onRangeChanged(HasData<Course> display) {
			getData();
		}
		
		private void getData() {
			ListService listService = ServiceFactory.getListService();
			listService.listCourses(user, new ResultNotifiable<LinkedList<Course>>() {

				@Override
				public void onSucces(LinkedList<Course> object) {
					updateRowData(0, object);
					updateRowCount(object.size(), true);
				}

				@Override
				public void onError(Throwable t) {
					Window.alert("Communication failed: " + t.getLocalizedMessage());
				}				
			});			
		}		
	}
	
	private static class SimpleMarkDataProvider extends RefreshableAsyncDataProvider<SimpleMarkUI> {
		private User user;
		private Course course;
				
		public SimpleMarkDataProvider(User user, Course course) {
			super();
			this.user = user;
			this.course = course;
		}

		@Override
		protected void onRangeChanged(HasData<SimpleMarkUI> display) {
			getData();
		}
		
		private void getData() {
			ListService listService = ServiceFactory.getListService();
			listService.listMarks(user, course, new ResultNotifiable<LinkedList<Mark>>() {

				@Override
				public void onSucces(LinkedList<Mark> object) {
					LinkedList<SimpleMarkUI> simpleMarkList = new LinkedList<SimpleMarkUI>();
					
					for (Mark mark : object) {
						simpleMarkList.add(new SimpleMarkUI(mark));
					}
					
					updateRowData(0, simpleMarkList);
					updateRowCount(simpleMarkList.size(), true);
				}

				@Override
				public void onError(Throwable t) {
					Window.alert("Communication failed: " + t.getLocalizedMessage());
				}				
			});						
		}		
	}
	
	private static class MarkDataProvider extends RefreshableAsyncDataProvider<Mark> {
		private String markId;
		
		public MarkDataProvider(String markId) {
			super();
			this.markId = markId;
		}

		@Override
		protected void onRangeChanged(HasData<Mark> display) {
			getData();
		}
		
		private void getData() {
			MarkService markService = ServiceFactory.getMarkService();
			markService.get(markId, new ResultNotifiable<Mark>() {

				@Override
				public void onSucces(Mark object) {
					LinkedList<Mark> markList = new LinkedList<Mark>();
					markList.add(object);
						
					updateRowData(0, markList);
					updateRowCount(markList.size(), true);
				}

				@Override
				public void onError(Throwable t) {
					Window.alert("Communication failed: " + t.getLocalizedMessage());	
				}				
			});			
		}		
	}
}
